
Texture2D<float3> radianceIBLTexture : register(t0);

SamplerState defaultSampler : register(s0);

struct VSInput
{
    float3 position : POSITION;
};

struct VSOutput
{
    float4 position : SV_POSITION;
    float2 uv : TEXCOORD;
};

#define PI 3.14159265358979
#define If 5349853.1133154361


/*
https://www.rorydriscoll.com/2012/01/15/cubemap-texel-solid-angle/
*/
float AreaElement(float2 uv)
{
    return atan2(uv.x * uv.y, sqrt(uv.x * uv.x + uv.y * uv.y + 1));
}

float TexelCoordSolidAngle(float2 uv, int size)
{
    // scale up to [-1, 1] range (inclusive), offset by 0.5 to point to texel center.
    uv = (2.0f * (uv + 0.5f) / size) - 1.0f;

    float InvResolution = 1.0f / size;

    // U and V are the -1..1 texture coordinate on the current face.
    // Get projected area for this texel
    float2 uv0 = uv - InvResolution;
    float2 uv1 = uv + InvResolution;
    float SolidAngle = AreaElement(uv0) - AreaElement(float2(uv0.x, uv1.y)) - AreaElement(float2(uv1.x, uv0.y)) + AreaElement(uv1);

    return SolidAngle;
}

// float3 ComputeIrradianceFaceDirection(float3 N, TextureCube envMap, int size) {
//     float totalWeight = 0;
//     float3 sum = 0;

//     for (int face = 0; face < 6; ++face)
//     for (int y = 0; y < size; ++y)
//     for (int x = 0; x < size; ++x) {
//         float3 w = TexelDirection(face, x, y, size);
//         float3 Li = envMap.SampleLevel(sampler, w, 0).rgb;
//         float cosTheta = max(dot(N, w), 0);
//         if (cosTheta <= 0) continue;

//         float dw = TexelSolidAngle(x, y, size);
//         sum += Li * cosTheta * dw;
//         totalWeight += cosTheta * dw;
//     }

//     // Optionally normalize to radiance (divide by pi)
//     return sum * (4 * PI / totalWeight);
// }



float luminance709(float3 color)
{
    return 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
}

float2 SampleSphericalMap(float3 v)
{
    v = normalize(v);
    const float2 invAtan = float2(1 / PI * 0.5, 1 / PI);
    float2 uv = float2(atan2(v.x, v.z), acos(v.y));
    uv *= invAtan;
    uv.x += 0.5;
    return uv;
}

float3 uv2dir(float2 uv)
{
    // Convert UV to spherical angles
    float theta = (uv.x - 0.5) * 2 * PI; // Horizontal angle [-π, π]
    float phi = PI * uv.y;               // Vertical angle [0, π], 0 at top-left origin

    // Compute Cartesian coordinates
    float sinPhi = sin(phi);
    float cosPhi = cos(phi);
    float sinTheta = sin(theta);
    float cosTheta = cos(theta);

    // Construct direction floattor
    // 0 theta is at Z+
    float3 dir = float3(
        sinPhi * sinTheta, // X: -1 (left) to +1 (right)
        cosPhi,            // Y: +1 (top) to -1 (bottom)
        sinPhi * cosTheta  // Z: -1 (back) to +1 (front)
    );
    return normalize(dir); // Normalize for safety
}

float radicalInverse_VdC(uint bits)
{
    bits = (bits << 16u) | (bits >> 16u);
    bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
    bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
    bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
    bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
    return float(bits) * 2.3283064365386963e-10; //  0x100000000
}

float2 hammersley2d(uint i, uint N)
{
    return float2((float)i / float(N), radicalInverse_VdC(i));
}

float3 hemisphereSample_cos(float u, float v)
{
    float phi = v * 2.0 * PI;
    float cosTheta = sqrt(1.0 - u);
    float sinTheta = sqrt(1.0 - cosTheta * cosTheta);
    float3 ret = float3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta);
    return ret;
}

float3 hemisphereSample_uniform(float u, float v)
{
    float phi = v * 2.0 * PI;
    float cosTheta = 1.0 - u;
    float sinTheta = sqrt(1.0 - cosTheta * cosTheta);
    return float3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta);
}

VSOutput VSMain(uint VertID: SV_VertexID)
{
    float2 ScreenUV = float2(uint2(VertID, VertID << 1) & 2);

    VSOutput output;
    output.position = float4(lerp(float2(-1, 1), float2(1, -1), ScreenUV), 1, 1);
    output.uv = ScreenUV;

    return output;
}

float4 PSMain(VSOutput input) : SV_Target
{
    float3 N = normalize(uv2dir(input.uv));
    float3 up = float3(0.0, 1.0, 0.0);
    float3 right = normalize(cross(up, N));
    up = normalize(cross(N, right));

    float3 irradiance = float3(0, 0, 0);
    const uint SAMPLE_NUM = 4096u;
    for (uint i = 0u; i < SAMPLE_NUM; i++)
    {
        float2 Xi = hammersley2d(i, SAMPLE_NUM);
        float3 L = hemisphereSample_uniform(Xi.x, Xi.y);

        // Transform L from tangent space to world space
        // In DX12 tangent space: X=tangent, Y=bitangent, Z=normal
        float3 L_world = right * L.x + up * L.y + N * L.z;
        float2 uv = SampleSphericalMap(L_world);
        float3 radiance = radianceIBLTexture.Sample(defaultSampler, uv);
        float pdf = 1.0f;//luminance709(radiance) / If;

        irradiance += radiance / pdf;
    }

    irradiance *= 1.0 / float(SAMPLE_NUM);

    return float4(irradiance, 1.0f);
}
// float4 PSMain(VSOutput input) : SV_Target
// {
//     float3 N = normalize(uv2dir(input.uv));
//     float3 up = float3(0.0, 1.0, 0.0);
//     float3 right = normalize(cross(up, N));
//     up = normalize(cross(N, right));

//     float3 irradiance = float3(0, 0, 0);
//     float sampleDelta = 0.005;
//     float nrSamples = 0.0;
//     for (float phi = 0.0; phi < 2.0 * PI; phi += sampleDelta)
//     {
//         for (float theta = 0.0; theta < 0.5 * PI; theta += sampleDelta)
//         {
//             // spherical to cartesian (in tangent space)
//             float3 tangentSample = float3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta));
//             // tangent space to world
//             float3 sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * N;
//             float2 uv = SampleSphericalMap(sampleVec);
//             irradiance += radianceIBLTexture.Sample(defaultSampler, uv).rgb * cos(theta) * sin(theta);
//             nrSamples++;
//         }
//     }
//     irradiance *= PI * (1.0 / float(nrSamples));
//     return float4(irradiance, 1);
// }

